Setup

Welcome to another tutorial for this class, COMP/STAT 112: Introduction to Data Science! It will be similar to the others, including demo videos and files embedded in this document and practice problems with hints or solutions at the end. There are some new libraries, so be sure to install those first.

As most of our files do, we start this one with three R code chunks: 1. options, 2. libraries and settings, 3. data.

knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE)
library(tidyverse)     # for data cleaning and plotting
library(googlesheets4) # for reading googlesheet data
library(lubridate)     # for date manipulation
library(openintro)     # for the abbr2state() function
library(palmerpenguins)# for Palmer penguin data
library(maps)          # for map data
library(ggmap)         # for mapping points on maps
library(gplots)        # for col2hex() function
library(RColorBrewer)  # for color palettes
library(sf)            # for working with spatial data
library(leaflet)       # for highly customizable mapping
library(ggthemes)      # for more themes (including theme_map())
gs4_deauth()           # To not have to authorize each time you knit.
theme_set(theme_minimal())
# Starbucks locations
Starbucks <- read_csv("https://www.macalester.edu/~ajohns24/Data/Starbucks.csv")

# Lisa's favorite St. Paul places - used in leaflet example
favorite_stp_by_lisa <- tibble(
  place = c("Home", "Macalester College", "Adams Spanish Immersion", 
            "Spirit Gymnastics", "Bama & Bapa", "Now Bikes",
            "Dance Spectrum", "Pizza Luce", "Brunson's"),
  long = c(-93.1405743, -93.1712321, -93.1451796, 
           -93.1650563, -93.1542883, -93.1696608, 
           -93.1393172, -93.1524256, -93.0753863),
  lat = c(44.950576, 44.9378965, 44.9237914,
          44.9654609, 44.9295072, 44.9436813, 
          44.9399922, 44.9468848, 44.9700727)
  )

Learning Goals

After this tutorial, you should be able to do the following:

  • Plot data points on top of a map using the ggmap() function along with ggplot2 functions.

  • Create choropleth maps using geom_map().

  • Add points and other ggplot2 features to a map created from geom_map().

  • Understand the basics of creating a map using leaflet, including adding points and choropleths to a base map.

Plotting points on a map

The Starbucks data, compiled by Danny Kaplan and provided by Alicia Johnson, contains information about every Starbucks in the world at the time the data were collected. It includes the Latitude and Longitude of each location. Let’s start by using familiar plotting tools

ggplot(data=Starbucks) +
  geom_point(aes(x = Longitude, y = Latitude), 
             alpha = 0.2, 
             size = .1)

The point pattern probably looks familiar. To highlight the geographical nature of this scatterplot, we can superimpose the points on top of a map, using the ggmap() function from the ggmap library.

NOTE: we used to be able to easily bring in Google maps. As of mid-2018, in order to bring those in, you need to have a registered API key. If you want to do that, see google_key in the help. Then, see the documentation for get_map(). We will bring in other types of maps since Google maps are harder to do now and require you to submit credit card information.

Instead, we bring in a stamen map (there are others you could try, but we’ll stick with this). You can also take a look at stamen maps on their website. First, let’s look at an example.

# Get the map information
world <- get_stamenmap(
    bbox = c(left = -180, bottom = -57, right = 179, top = 82.1), 
    maptype = "terrain",
    zoom = 2)

# Plot the points on the map
ggmap(world) + # creates the map "background"
  geom_point(data = Starbucks, 
             aes(x = Longitude, y = Latitude), 
             alpha = .3, 
             size = .1) +
  theme_map()

Next, we will walk through the get_stamenmap() function arguments. The code below is what was used to get the world map information.

get_stamenmap(
    bbox = c(left = -180, bottom = -57, right = 179, top = 82.1), 
    maptype = "terrain",
    zoom = 2)

bbox

get_stamenmap(
    bbox = c(left = -180, bottom = -57, right = 179, top = 82.1),
    maptype = "terrain",
    zoom = 2)

The bbox argument tells it the minimum and maximum latitude and longitude points. So, left is the minimum longitude, right is the maximum longitude, bottom is the minimum latitude, and top is the maximum latitude. I found it helpful to go to openstreetmap: zoom in on the area of interest, click export, and you will see all the values you need. I had to modify them slightly, which you can do after your initial plot.

maptype

get_stamenmap(
    bbox = c(left = -180, bottom = -57, right = 179, top = 82.1),
    maptype = "terrain",
    zoom = 2)

The maptype tells it the style of the map. Check out the different options by looking in the get_stamenmap help (type ?get_stamenmap in the console).

zoom

get_stamenmap(
    bbox = c(left = -180, bottom = -57, right = 179, top = 82.1),
    maptype = "terrain",
    zoom = 2)

When you make a large area, you need to decrease the zoom, otherwise it will take too long to load. So, it’s a good idea to start with a small zoom and you can always make it bigger if you want. This might seem counter-intuitive at first. I think of the zoom level as the level of detail. So, smaller numbers show less detail and larger numbers more detail. I often go to the stamanmaps webpage and search for the location I’m mapping. Then, in the URL, you can see the zoom number. For example, this link is a map of St. Paul: http://maps.stamen.com/#terrain/12/44.9531/-93.0904. Notice the number 12 next to /#terrain/. That means it is zoomed in at 12.

ggmap()

We save the the map information from get_stamenmap() to a named value and then use it in ggmap():

# Get the map information
world <- get_stamenmap(
    bbox = c(left = -180, bottom = -57, right = 179, top = 82.1),
    maptype = "terrain",
    zoom = 2)

# Plot the points on the map
ggmap(world) + # creates the map "background"
  geom_point(data = Starbucks,
             aes(x = Longitude, y = Latitude),
             alpha = .3,
             size = .1) +
  theme_map()

The ggmap() function will print the “background” map. Think of it as the providing the canvas on which we will plot. This takes the place of our usual ggplot().

ggmap(world)

After that, we can use the geom_XXX() functions from ggplot2 that we are used to in order to put points, lines, etc. on top of the map. But, we need to remember to also provide the data we are using in the geom_XXX() function(s) we use since we do not have the ggplot() function in which to provide it.

# Get the map information
world <- get_stamenmap(
    bbox = c(left = -180, bottom = -57, right = 179, top = 82.1),
    maptype = "terrain",
    zoom = 2)

# Plot the points on the map
ggmap(world) + # creates the map "background"
  geom_point(data = Starbucks,
             aes(x = Longitude, y = Latitude),
             alpha = .3,
             size = .1) +
  theme_map()

theme_map()

The last thing I did in the code was to add theme_map(). This is optional, but I often find it makes it look nice.

# Get the map information
world <- get_stamenmap(
    bbox = c(left = -180, bottom = -57, right = 179, top = 82.1),
    maptype = "terrain",
    zoom = 2)

# Plot the points on the map
ggmap(world) + # creates the map "background"
  geom_point(data = Starbucks,
             aes(x = Longitude, y = Latitude),
             alpha = .3,
             size = .1) +
  theme_map()

So, the final map as a world map as the background with points plotted on top that show the Starbucks locations. The points are .1 of their usual size and have a transparency level of .3.

Demo video

With that introduction, you are ready to watch the demo video!

Voicethread: ggmap demo

Resources

Your turn!

Exercise: More with Starbucks

  1. Add an aesthetic to the world map that sets the color of the points according to the ownership type. What, if anything, can you deduce from this visualization?

  2. Construct a new map of Starbucks locations in the Twin Cities metro area (approximately the 5 county metro area).

  3. In the Twin Cities plot, play with the zoom number. What does it do? (just describe what it does - don’t actually include more than one map).

  4. Try a couple different map types (see get_stamenmap() in help and look at maptype). Include a map with one of the other map types.

  5. Add a point to the map that indicates Macalester College and label it appropriately. There are many ways you can do think, but I think it’s easiest with the annotate() function (see ggplot2 cheatsheet).

Choropleths

Geographical data needn’t be expressed by latitude and longitude. For choropleth maps, instead of visualizing our data as points with different aesthetics (size, color, transparency, etc.), we color different regions of the maps based on data values. To do this we need to specify both the geometric regions on which the data resides (counties, states, zip codes, etc.), and then wrangle the data so that there is one value per region.

Let’s return to the Starbucks data. First, we will create a new dataset, starbucks_us_by_state that limits the data to the US, finds the number of Starbucks in each state, and creates a state name that is in all lowercase letters that matches the state name in the region variable of the states_map dataset.

The states_map dataset gives information about creating the borders of the US states. The data is retrieved using the map_data() function. Run ?map_data in the console to see more information about what other maps are available. There are also other packages that provide different types of maps.

Then, we can use geom_map() to create a choropleth map. Let’s take a look at the map and we’ll go through the details after.

#Create a new Starbucks dataset that 
# - filters to the US
# - summarizes the number of Starbucks in each state
# - has full names of states in lowercase letters (to match to states_map data created next)

starbucks_us_by_state <- Starbucks %>% 
  filter(Country == "US") %>% 
  count(`State/Province`) %>% 
  mutate(state_name = str_to_lower(abbr2state(`State/Province`))) 

#US states map information - coordinates used to draw borders
states_map <- map_data("state")

# map that colors state by number of Starbucks
starbucks_us_by_state %>% 
  ggplot() +
  geom_map(map = states_map,
           aes(map_id = state_name,
               fill = n)) +
  #This assures the map looks decently nice:
  expand_limits(x = states_map$long, y = states_map$lat) + 
  theme_map()

Now, let’s look more closely at what each piece of the code below is doing.

starbucks_us_by_state %>% 
  ggplot() +
  geom_map(map = states_map,
           aes(map_id = state_name,
               fill = n)) +
  expand_limits(x = states_map$long, y = states_map$lat) + 
  theme_map()

Choose a map

The map argument tells R at which level to create the map. Really, it tells it how to draw all the borders This is a very special data set. According to the geom_map() documentation, it is a “data frame that contains the map coordinates … It must contain columns x or long, y or lat, and region or id.” We are using the map_data() function to create the map file (see above for more detail0. You can open the map data, states_map, and see that it adheres to the rules.

starbucks_us_by_state %>% 
  ggplot() +
  geom_map(map = states_map,
           aes(map_id = state_name,
               fill = n)) +
  expand_limits(x = states_map$long, y = states_map$lat) +
  theme_map()

Connect map id/region variable to data being plotted

The map_id inside of aes() is a required aesthetic for the geom_map() geom. It tells R which variable is the region/id variable, in this case the state. It connects the region or id from the map (region variable in states_map dataset, in this example) to the dataset being plotted (state_name in starbucks_us_by_state, in this example). So state_name needs to have the same form as region, which is why we modified the state names in starbucks_us_by_state.

starbucks_us_by_state %>% 
  ggplot() +
  geom_map(map = states_map,
           aes(map_id = state_name,
               fill = n)) +
  expand_limits(x = states_map$long, y = states_map$lat) +
  theme_map()

Use ggplot2 features

We tell it to fill in the states by the variable n, the number of Starbucks in each state. With the geom_map() geom, it will fill in the borders of the regions we defined in the map argument.

starbucks_us_by_state %>% 
  ggplot() +
  geom_map(map = states_map,
           aes(map_id = state_name,
               fill = n)) +
  expand_limits(x = states_map$long, y = states_map$lat) +
  theme_map()

expand_limits()

Use expand_limits() to assure that the map covers the entire area it’s supposed to. We put the longitude variable from states_map for the x argument and the latitude variable from states_map for the y argument to assure the map stretches across the entire range of longitudes and latitudes in the map. There may be a better way to do this but I have yet to find it, and when I leave it out, I don’t even see the map.

starbucks_us_by_state %>% 
  ggplot() +
  geom_map(map = states_map,
           aes(map_id = state_name,
               fill = n)) +
  expand_limits(x = states_map$long, y = states_map$lat) +
  theme_map()

theme_map()

This is a personal preference. I like the way theme_map() makes the map look.

starbucks_us_by_state %>% 
  ggplot() +
  geom_map(map = states_map,
           aes(map_id = state_name,
               fill = n)) +
  expand_limits(x = states_map$long, y = states_map$lat) +
  theme_map()

Add ggplot2 layers

You can add any of the ggplot2 layers on top of this map. In this example, I’ve added the MN Starbucks as points, a title, and changed the legend background (so it doesn’t have one and overlap California).

starbucks_us_by_state %>% 
  ggplot() +
  geom_map(map = states_map,
           aes(map_id = state_name,
               fill = n)) +
  geom_point(data = Starbucks %>% filter(`State/Province` == "MN"),
             aes(x = Longitude, y = Latitude),
             size = .05,
             alpha = .2, 
             color = "goldenrod") +
  expand_limits(x = states_map$long, y = states_map$lat) + 
  labs(title = "Starbucks in MN") +
  theme_map() +
  theme(legend.background = element_blank())

Resources

Your turn!

Exercise: Even more with Starbucks

The example I showed did not account for population of each state in the map. In the code below, a new variable is created, starbucks_per_10000, that gives the number of Starbucks per 10,000 people. It is in the starbucks_with_2018_pop_est dataset.

census_pop_est_2018 <- read_csv("https://www.dropbox.com/s/6txwv3b4ng7pepe/us_census_2018_state_pop_est.csv?dl=1") %>% 
  separate(state, into = c("dot","state"), extra = "merge") %>% 
  select(-dot) %>% 
  mutate(state = str_to_lower(state))

starbucks_with_2018_pop_est <-
  starbucks_us_by_state %>% 
  left_join(census_pop_est_2018,
            by = c("state_name" = "state")) %>% 
  mutate(starbucks_per_10000 = (n/est_pop_2018)*10000)
  1. dplyr review: Look through the code above and describe what each line of code does.

  2. Create a choropleth map that shows the number of Starbucks per 10,000 people on a map of the US. Use a new fill color, add points for all Starbucks in the US (except Hawaii and Alaska), add an informative title for the plot, and include a caption that says who created the plot (you!). Make a conclusion about what you observe.

Using leaflet to create maps

Concept Map

Leaflet is an open-source JavaScript library for creating maps. It can be used outside of R, but we will only discuss using the leaflet library in R.

This library uses a different plotting framework from ggplot2 although it still has a tidyverse feel due to its use of the pipe, %>% and the way it adds layers to the plot, just like in ggplot2.

Introductory video

Watch the video that introduces leaflet() functions.

Voicethread: Mapping in R with leaflet

Steps to create a map

  1. Create a map widget by calling leaflet() and telling it the data to use.
  2. Add a base map using addTiles() (the default) or addProviderTiles().
  3. Add layers to the map by using layer functions (e.g. , addMarkers(), addPolygons()) to modify the map widget.
  4. Repeat step 3 as desired.
  5. Print the map widget to display it.

Creating a map

Now, I create a basic map and add my points (the points are a layer on the map). The data are in favorite_stp_by_lisa.

The function we will use to create the maps will look for certain variable names for latitude (lat, latitude) and longitude (lng, long, or longitude). If you do not name them one of those things or if the data you are using doesn’t name them that, you need to call out the name explicitly. You can use a “two-finger scroll” to zoom in and out.

leaflet(data = favorite_stp_by_lisa) %>% #base plot
  addTiles() %>% #base map - default is openstreet map but that can be changed
  addMarkers() #Adds markers - knows lat and long from names in data

Same as above but explicitly told it latitude and longitude, which you would need to do if those variables had a name not recognized by the function, and added labels. WARNING: DO NOT FORGET THE ~ BEFORE THE VARIABLE NAMES!!!

leaflet(data = favorite_stp_by_lisa) %>% 
  addTiles() %>% 
  addMarkers(lng = ~long, lat = ~lat, label = ~place) 

We can change just about everything about our map. This is the same plot as above with some aesthetic changes. Some tips:

  • To see all available provider base maps, type providers in the console.

  • To access those maps, use providers$PROVIDERNAME inside the addProviderTiles() function, where PROVIDERNAME is one of those listed providers. When you type provider$ a list should show up that you can click on.

  • Colors need to be in “hex” form. I used the col2hex() function from the gplot library to do that since I don’t have any hex colors memorized. You can also google hex colors and find websites to help you out.

  • Search addControl in the Help or type ?addControl into the console to see what all the arguments mean and how you can change them.

leaflet(data = favorite_stp_by_lisa) %>% 
  addProviderTiles(providers$Stamen.Watercolor) %>% 
  addCircles(lng = ~long, 
             lat = ~lat, 
             label = ~place, 
             weight = 10, 
             opacity = 1, color = col2hex("darkblue")) 

The map below is also the “same” as the ones I have already created with a new base map and a line to trace my route, which was created with the addPolylines() layer. It traces it in the order they are entered in the dataset.

leaflet(data = favorite_stp_by_lisa) %>% 
  addProviderTiles(providers$CartoDB.DarkMatter) %>% 
  addCircles(lng = ~long, 
             lat = ~lat, 
             label = ~place, 
             weight = 10, 
             opacity = 1, 
             color = col2hex("darkred")) %>% 
  addPolylines(lng = ~long, 
               lat = ~lat, 
               color = col2hex("darkred"))

Choropleth layers with addPolygons()

LS0tCnRpdGxlOiAiTWFwcGluZyBkYXRhIGluIFIiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBkZl9wcmludDogcGFnZWQKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKLS0tCgojIyBTZXR1cAoKV2VsY29tZSB0byBhbm90aGVyIHR1dG9yaWFsIGZvciB0aGlzIGNsYXNzLCBDT01QL1NUQVQgMTEyOiAqSW50cm9kdWN0aW9uIHRvIERhdGEgU2NpZW5jZSohIEl0IHdpbGwgYmUgc2ltaWxhciB0byB0aGUgb3RoZXJzLCBpbmNsdWRpbmcgZGVtbyB2aWRlb3MgYW5kIGZpbGVzIGVtYmVkZGVkIGluIHRoaXMgZG9jdW1lbnQgYW5kIHByYWN0aWNlIHByb2JsZW1zIHdpdGggaGludHMgb3Igc29sdXRpb25zIGF0IHRoZSBlbmQuIFRoZXJlIGFyZSBzb21lIG5ldyBsaWJyYXJpZXMsIHNvIGJlIHN1cmUgdG8gaW5zdGFsbCB0aG9zZSBmaXJzdC4KCkFzIG1vc3Qgb2Ygb3VyIGZpbGVzIGRvLCB3ZSBzdGFydCB0aGlzIG9uZSB3aXRoIHRocmVlIFIgY29kZSBjaHVua3M6IDEuIG9wdGlvbnMsIDIuIGxpYnJhcmllcyBhbmQgc2V0dGluZ3MsIDMuIGRhdGEuIAoKYGBge3Igc2V0dXB9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UpCmBgYAoKYGBge3IgbGlicmFyaWVzfQpsaWJyYXJ5KHRpZHl2ZXJzZSkgICAgICMgZm9yIGRhdGEgY2xlYW5pbmcgYW5kIHBsb3R0aW5nCmxpYnJhcnkoZ29vZ2xlc2hlZXRzNCkgIyBmb3IgcmVhZGluZyBnb29nbGVzaGVldCBkYXRhCmxpYnJhcnkobHVicmlkYXRlKSAgICAgIyBmb3IgZGF0ZSBtYW5pcHVsYXRpb24KbGlicmFyeShvcGVuaW50cm8pICAgICAjIGZvciB0aGUgYWJicjJzdGF0ZSgpIGZ1bmN0aW9uCmxpYnJhcnkocGFsbWVycGVuZ3VpbnMpIyBmb3IgUGFsbWVyIHBlbmd1aW4gZGF0YQpsaWJyYXJ5KG1hcHMpICAgICAgICAgICMgZm9yIG1hcCBkYXRhCmxpYnJhcnkoZ2dtYXApICAgICAgICAgIyBmb3IgbWFwcGluZyBwb2ludHMgb24gbWFwcwpsaWJyYXJ5KGdwbG90cykgICAgICAgICMgZm9yIGNvbDJoZXgoKSBmdW5jdGlvbgpsaWJyYXJ5KFJDb2xvckJyZXdlcikgICMgZm9yIGNvbG9yIHBhbGV0dGVzCmxpYnJhcnkoc2YpICAgICAgICAgICAgIyBmb3Igd29ya2luZyB3aXRoIHNwYXRpYWwgZGF0YQpsaWJyYXJ5KGxlYWZsZXQpICAgICAgICMgZm9yIGhpZ2hseSBjdXN0b21pemFibGUgbWFwcGluZwpsaWJyYXJ5KGdndGhlbWVzKSAgICAgICMgZm9yIG1vcmUgdGhlbWVzIChpbmNsdWRpbmcgdGhlbWVfbWFwKCkpCmdzNF9kZWF1dGgoKSAgICAgICAgICAgIyBUbyBub3QgaGF2ZSB0byBhdXRob3JpemUgZWFjaCB0aW1lIHlvdSBrbml0Lgp0aGVtZV9zZXQodGhlbWVfbWluaW1hbCgpKQpgYGAKCmBgYHtyIG15X2xpYnJhcmllcywgaW5jbHVkZT1GQUxTRX0KIyBMaXNhIG5lZWRzIHRoaXMsIHN0dWRlbnRzIGRvbid0CmxpYnJhcnkoZG93bmxvYWR0aGlzKSAjIGZvciBpbmNsdWRpbmcgZG93bmxvYWQgYnV0dG9ucyBmb3IgZmlsZXMKbGlicmFyeShmbGFpcikgIyBmb3IgaGlnaGxpZ2h0aW5nIGNvZGUKYGBgCgpgYGB7ciBkYXRhfQojIFN0YXJidWNrcyBsb2NhdGlvbnMKU3RhcmJ1Y2tzIDwtIHJlYWRfY3N2KCJodHRwczovL3d3dy5tYWNhbGVzdGVyLmVkdS9+YWpvaG5zMjQvRGF0YS9TdGFyYnVja3MuY3N2IikKCiMgTGlzYSdzIGZhdm9yaXRlIFN0LiBQYXVsIHBsYWNlcyAtIHVzZWQgaW4gbGVhZmxldCBleGFtcGxlCmZhdm9yaXRlX3N0cF9ieV9saXNhIDwtIHRpYmJsZSgKICBwbGFjZSA9IGMoIkhvbWUiLCAiTWFjYWxlc3RlciBDb2xsZWdlIiwgIkFkYW1zIFNwYW5pc2ggSW1tZXJzaW9uIiwgCiAgICAgICAgICAgICJTcGlyaXQgR3ltbmFzdGljcyIsICJCYW1hICYgQmFwYSIsICJOb3cgQmlrZXMiLAogICAgICAgICAgICAiRGFuY2UgU3BlY3RydW0iLCAiUGl6emEgTHVjZSIsICJCcnVuc29uJ3MiKSwKICBsb25nID0gYygtOTMuMTQwNTc0MywgLTkzLjE3MTIzMjEsIC05My4xNDUxNzk2LCAKICAgICAgICAgICAtOTMuMTY1MDU2MywgLTkzLjE1NDI4ODMsIC05My4xNjk2NjA4LCAKICAgICAgICAgICAtOTMuMTM5MzE3MiwgLTkzLjE1MjQyNTYsIC05My4wNzUzODYzKSwKICBsYXQgPSBjKDQ0Ljk1MDU3NiwgNDQuOTM3ODk2NSwgNDQuOTIzNzkxNCwKICAgICAgICAgIDQ0Ljk2NTQ2MDksIDQ0LjkyOTUwNzIsIDQ0Ljk0MzY4MTMsIAogICAgICAgICAgNDQuOTM5OTkyMiwgNDQuOTQ2ODg0OCwgNDQuOTcwMDcyNykKICApCmBgYAoKIyMgTGVhcm5pbmcgR29hbHMKCkFmdGVyIHRoaXMgdHV0b3JpYWwsIHlvdSBzaG91bGQgYmUgYWJsZSB0byBkbyB0aGUgZm9sbG93aW5nOgoKKiBQbG90IGRhdGEgcG9pbnRzIG9uIHRvcCBvZiBhIG1hcCB1c2luZyB0aGUgYGdnbWFwKClgIGZ1bmN0aW9uIGFsb25nIHdpdGggYGdncGxvdDJgIGZ1bmN0aW9ucy4gIAoKKiBDcmVhdGUgY2hvcm9wbGV0aCBtYXBzIHVzaW5nIGBnZW9tX21hcCgpYC4gIAoKKiBBZGQgcG9pbnRzIGFuZCBvdGhlciBgZ2dwbG90MmAgZmVhdHVyZXMgdG8gYSBtYXAgY3JlYXRlZCBmcm9tIGBnZW9tX21hcCgpYC4gIAoKKiBVbmRlcnN0YW5kIHRoZSBiYXNpY3Mgb2YgY3JlYXRpbmcgYSBtYXAgdXNpbmcgYGxlYWZsZXRgLCBpbmNsdWRpbmcgYWRkaW5nIHBvaW50cyBhbmQgY2hvcm9wbGV0aHMgdG8gYSBiYXNlIG1hcC4KCgojIyBQbG90dGluZyBwb2ludHMgb24gYSBtYXAKClRoZSBgU3RhcmJ1Y2tzYCBkYXRhLCBjb21waWxlZCBieSBEYW5ueSBLYXBsYW4gYW5kIHByb3ZpZGVkIGJ5IEFsaWNpYSBKb2huc29uLCBjb250YWlucyBpbmZvcm1hdGlvbiBhYm91dCBldmVyeSBTdGFyYnVja3MgaW4gdGhlIHdvcmxkIGF0IHRoZSB0aW1lIHRoZSBkYXRhIHdlcmUgY29sbGVjdGVkLiBJdCBpbmNsdWRlcyB0aGUgYExhdGl0dWRlYCBhbmQgYExvbmdpdHVkZWAgb2YgZWFjaCBsb2NhdGlvbi4gIExldCdzIHN0YXJ0IGJ5IHVzaW5nIGZhbWlsaWFyIHBsb3R0aW5nIHRvb2xzCgpgYGB7cn0KZ2dwbG90KGRhdGE9U3RhcmJ1Y2tzKSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IExvbmdpdHVkZSwgeSA9IExhdGl0dWRlKSwgCiAgICAgICAgICAgICBhbHBoYSA9IDAuMiwgCiAgICAgICAgICAgICBzaXplID0gLjEpCmBgYAoKVGhlIHBvaW50IHBhdHRlcm4gcHJvYmFibHkgbG9va3MgZmFtaWxpYXIuICBUbyBoaWdobGlnaHQgdGhlIGdlb2dyYXBoaWNhbCBuYXR1cmUgb2YgdGhpcyBzY2F0dGVycGxvdCwgd2UgY2FuIHN1cGVyaW1wb3NlIHRoZSBwb2ludHMgb24gdG9wIG9mIGEgbWFwLCB1c2luZyB0aGUgYGdnbWFwKClgIGZ1bmN0aW9uIGZyb20gdGhlIGBnZ21hcGAgbGlicmFyeS4gCgoqKk5PVEUqKjogd2UgdXNlZCB0byBiZSBhYmxlIHRvICplYXNpbHkqIGJyaW5nIGluIEdvb2dsZSBtYXBzLiBBcyBvZiBtaWQtMjAxOCwgaW4gb3JkZXIgdG8gYnJpbmcgdGhvc2UgaW4sIHlvdSBuZWVkIHRvIGhhdmUgYSByZWdpc3RlcmVkIEFQSSBrZXkuIElmIHlvdSB3YW50IHRvIGRvIHRoYXQsIHNlZSBgZ29vZ2xlX2tleWAgaW4gdGhlIGhlbHAuIFRoZW4sIHNlZSB0aGUgZG9jdW1lbnRhdGlvbiBmb3IgYGdldF9tYXAoKWAuIFdlIHdpbGwgYnJpbmcgaW4gb3RoZXIgdHlwZXMgb2YgbWFwcyBzaW5jZSBHb29nbGUgbWFwcyBhcmUgaGFyZGVyIHRvIGRvIG5vdyBhbmQgcmVxdWlyZSB5b3UgdG8gc3VibWl0IGNyZWRpdCBjYXJkIGluZm9ybWF0aW9uLgoKSW5zdGVhZCwgd2UgYnJpbmcgaW4gYSBzdGFtZW4gbWFwICh0aGVyZSBhcmUgb3RoZXJzIHlvdSBjb3VsZCB0cnksIGJ1dCB3ZSdsbCBzdGljayB3aXRoIHRoaXMpLiBZb3UgY2FuIGFsc28gdGFrZSBhIGxvb2sgYXQgc3RhbWVuIG1hcHMgb24gdGhlaXIgW3dlYnNpdGVdKGh0dHA6Ly9tYXBzLnN0YW1lbi5jb20vI3dhdGVyY29sb3IvMTIvMzcuNzcwNi8tMTIyLjM3ODIpLiBGaXJzdCwgbGV0J3MgbG9vayBhdCBhbiBleGFtcGxlLiAKCmBgYHtyIHN0YXJidWNrcy1tYXB9CiMgR2V0IHRoZSBtYXAgaW5mb3JtYXRpb24Kd29ybGQgPC0gZ2V0X3N0YW1lbm1hcCgKICAgIGJib3ggPSBjKGxlZnQgPSAtMTgwLCBib3R0b20gPSAtNTcsIHJpZ2h0ID0gMTc5LCB0b3AgPSA4Mi4xKSwgCiAgICBtYXB0eXBlID0gInRlcnJhaW4iLAogICAgem9vbSA9IDIpCgojIFBsb3QgdGhlIHBvaW50cyBvbiB0aGUgbWFwCmdnbWFwKHdvcmxkKSArICMgY3JlYXRlcyB0aGUgbWFwICJiYWNrZ3JvdW5kIgogIGdlb21fcG9pbnQoZGF0YSA9IFN0YXJidWNrcywgCiAgICAgICAgICAgICBhZXMoeCA9IExvbmdpdHVkZSwgeSA9IExhdGl0dWRlKSwgCiAgICAgICAgICAgICBhbHBoYSA9IC4zLCAKICAgICAgICAgICAgIHNpemUgPSAuMSkgKwogIHRoZW1lX21hcCgpCmBgYAoKTmV4dCwgd2Ugd2lsbCB3YWxrIHRocm91Z2ggdGhlIGBnZXRfc3RhbWVubWFwKClgIGZ1bmN0aW9uIGFyZ3VtZW50cy4gVGhlIGNvZGUgYmVsb3cgaXMgd2hhdCB3YXMgdXNlZCB0byBnZXQgdGhlIHdvcmxkIG1hcCBpbmZvcm1hdGlvbi4KCmBgYHtyIGdldF9zdGFtZW5tYXAtY29kZSwgZXZhbD1GQUxTRX0KZ2V0X3N0YW1lbm1hcCgKICAgIGJib3ggPSBjKGxlZnQgPSAtMTgwLCBib3R0b20gPSAtNTcsIHJpZ2h0ID0gMTc5LCB0b3AgPSA4Mi4xKSwgCiAgICBtYXB0eXBlID0gInRlcnJhaW4iLAogICAgem9vbSA9IDIpCmBgYAoKKipgYmJveGAqKgoKYGBge3IsIGVjaG89RkFMU0V9CmRlY29yYXRlX2NodW5rKCJnZXRfc3RhbWVubWFwLWNvZGUiLCBldmFsID0gRkFMU0UpICU+JSAKICBmbGFpcigiYmJveCA9ICIpCmBgYAoKClRoZSBgYmJveGAgYXJndW1lbnQgdGVsbHMgaXQgdGhlIG1pbmltdW0gYW5kIG1heGltdW0gbGF0aXR1ZGUgYW5kIGxvbmdpdHVkZSBwb2ludHMuIFNvLCBsZWZ0IGlzIHRoZSBtaW5pbXVtIGxvbmdpdHVkZSwgcmlnaHQgaXMgdGhlIG1heGltdW0gbG9uZ2l0dWRlLCBib3R0b20gaXMgdGhlIG1pbmltdW0gbGF0aXR1ZGUsIGFuZCB0b3AgaXMgdGhlIG1heGltdW0gbGF0aXR1ZGUuIEkgZm91bmQgaXQgaGVscGZ1bCB0byBnbyB0byBbb3BlbnN0cmVldG1hcF0oaHR0cHM6Ly93d3cub3BlbnN0cmVldG1hcC5vcmcpOiB6b29tIGluIG9uIHRoZSBhcmVhIG9mIGludGVyZXN0LCBjbGljayBleHBvcnQsIGFuZCB5b3Ugd2lsbCBzZWUgYWxsIHRoZSB2YWx1ZXMgeW91IG5lZWQuIEkgaGFkIHRvIG1vZGlmeSB0aGVtIHNsaWdodGx5LCB3aGljaCB5b3UgY2FuIGRvIGFmdGVyIHlvdXIgaW5pdGlhbCBwbG90LgoKKipgbWFwdHlwZWAqKgoKYGBge3IsIGVjaG89RkFMU0V9CmRlY29yYXRlX2NodW5rKCJnZXRfc3RhbWVubWFwLWNvZGUiLCBldmFsID0gRkFMU0UpICU+JSAKICBmbGFpcigibWFwdHlwZSA9ICIpCmBgYAoKVGhlIGBtYXB0eXBlYCB0ZWxscyBpdCB0aGUgc3R5bGUgb2YgdGhlIG1hcC4gQ2hlY2sgb3V0IHRoZSBkaWZmZXJlbnQgb3B0aW9ucyBieSBsb29raW5nIGluIHRoZSBgZ2V0X3N0YW1lbm1hcGAgaGVscCAodHlwZSBgP2dldF9zdGFtZW5tYXBgIGluIHRoZSBjb25zb2xlKS4KCioqYHpvb21gKioKCmBgYHtyLCBlY2hvPUZBTFNFfQpkZWNvcmF0ZV9jaHVuaygiZ2V0X3N0YW1lbm1hcC1jb2RlIiwgZXZhbCA9IEZBTFNFKSAlPiUgCiAgZmxhaXIoInpvb20gPSAiKQpgYGAKCldoZW4geW91IG1ha2UgYSBsYXJnZSBhcmVhLCB5b3UgbmVlZCB0byBkZWNyZWFzZSB0aGUgem9vbSwgb3RoZXJ3aXNlIGl0IHdpbGwgdGFrZSB0b28gbG9uZyB0byBsb2FkLiBTbywgaXQncyBhIGdvb2QgaWRlYSB0byBzdGFydCB3aXRoIGEgc21hbGwgem9vbSBhbmQgeW91IGNhbiBhbHdheXMgbWFrZSBpdCBiaWdnZXIgaWYgeW91IHdhbnQuIFRoaXMgbWlnaHQgc2VlbSBjb3VudGVyLWludHVpdGl2ZSBhdCBmaXJzdC4gSSB0aGluayBvZiB0aGUgem9vbSBsZXZlbCBhcyB0aGUgbGV2ZWwgb2YgZGV0YWlsLiBTbywgc21hbGxlciBudW1iZXJzIHNob3cgbGVzcyBkZXRhaWwgYW5kIGxhcmdlciBudW1iZXJzIG1vcmUgZGV0YWlsLiBJIG9mdGVuIGdvIHRvIHRoZSBzdGFtYW5tYXBzIHdlYnBhZ2UgYW5kIHNlYXJjaCBmb3IgdGhlIGxvY2F0aW9uIEknbSBtYXBwaW5nLiBUaGVuLCBpbiB0aGUgVVJMLCB5b3UgY2FuIHNlZSB0aGUgem9vbSBudW1iZXIuIEZvciBleGFtcGxlLCB0aGlzIGxpbmsgIGlzIGEgbWFwIG9mIFN0LiBQYXVsOiBbaHR0cDovL21hcHMuc3RhbWVuLmNvbS8jdGVycmFpbi8xMi80NC45NTMxLy05My4wOTA0XShodHRwOi8vbWFwcy5zdGFtZW4uY29tLyN0ZXJyYWluLzEyLzQ0Ljk1MzEvLTkzLjA5MDQpLiBOb3RpY2UgdGhlIG51bWJlciBgMTJgIG5leHQgdG8gIGAvI3RlcnJhaW4vYC4gVGhhdCBtZWFucyBpdCBpcyB6b29tZWQgaW4gYXQgMTIuIAoKKipgZ2dtYXAoKWAqKgoKV2Ugc2F2ZSB0aGUgdGhlIG1hcCBpbmZvcm1hdGlvbiBmcm9tIGBnZXRfc3RhbWVubWFwKClgIHRvIGEgbmFtZWQgdmFsdWUgYW5kIHRoZW4gdXNlIGl0IGluIGBnZ21hcCgpYDoKCmBgYHtyLCBlY2hvPUZBTFNFfQpkZWNvcmF0ZV9jaHVuaygic3RhcmJ1Y2tzLW1hcCIsIGV2YWwgPSBGQUxTRSkgJT4lIAogIGZsYWlyKCJ3b3JsZCA8LSIpICU+JSAKICBmbGFpcigid29ybGQiKQpgYGAKClRoZSBgZ2dtYXAoKWAgZnVuY3Rpb24gd2lsbCBwcmludCB0aGUgImJhY2tncm91bmQiIG1hcC4gVGhpbmsgb2YgaXQgYXMgdGhlIHByb3ZpZGluZyB0aGUgY2FudmFzIG9uIHdoaWNoIHdlIHdpbGwgcGxvdC4gVGhpcyB0YWtlcyB0aGUgcGxhY2Ugb2Ygb3VyIHVzdWFsIGBnZ3Bsb3QoKWAuCgpgYGB7cn0KZ2dtYXAod29ybGQpCmBgYAoKQWZ0ZXIgdGhhdCwgd2UgY2FuIHVzZSB0aGUgYGdlb21fWFhYKClgIGZ1bmN0aW9ucyBmcm9tIGBnZ3Bsb3QyYCB0aGF0IHdlIGFyZSB1c2VkIHRvIGluIG9yZGVyIHRvIHB1dCBwb2ludHMsIGxpbmVzLCBldGMuIG9uIHRvcCBvZiB0aGUgbWFwLiBCdXQsIHdlIG5lZWQgdG8gcmVtZW1iZXIgdG8gYWxzbyBwcm92aWRlIHRoZSBkYXRhIHdlIGFyZSB1c2luZyBpbiB0aGUgYGdlb21fWFhYKClgIGZ1bmN0aW9uKHMpIHdlIHVzZSBzaW5jZSB3ZSBkbyBub3QgaGF2ZSB0aGUgYGdncGxvdCgpYCBmdW5jdGlvbiBpbiB3aGljaCB0byBwcm92aWRlIGl0LiAKCmBgYHtyLCBlY2hvPUZBTFNFfQpkZWNvcmF0ZV9jaHVuaygic3RhcmJ1Y2tzLW1hcCIsIGV2YWwgPSBGQUxTRSkgJT4lIAogIGZsYWlyKCJkYXRhID0gU3RhcmJ1Y2tzIikKYGBgCgoKKipgdGhlbWVfbWFwKClgKioKClRoZSBsYXN0IHRoaW5nIEkgZGlkIGluIHRoZSBjb2RlIHdhcyB0byBhZGQgYHRoZW1lX21hcCgpYC4gVGhpcyBpcyBvcHRpb25hbCwgYnV0IEkgb2Z0ZW4gZmluZCBpdCBtYWtlcyBpdCBsb29rIG5pY2UuCgpgYGB7ciwgZWNobz1GQUxTRX0KZGVjb3JhdGVfY2h1bmsoInN0YXJidWNrcy1tYXAiKSAlPiUgCiAgZmxhaXIoInRoZW1lX21hcCgpIikKYGBgCgpTbywgdGhlIGZpbmFsIG1hcCBhcyBhIHdvcmxkIG1hcCBhcyB0aGUgYmFja2dyb3VuZCB3aXRoIHBvaW50cyBwbG90dGVkIG9uIHRvcCB0aGF0IHNob3cgdGhlIFN0YXJidWNrcyBsb2NhdGlvbnMuIFRoZSBwb2ludHMgYXJlIC4xIG9mIHRoZWlyIHVzdWFsIHNpemUgYW5kIGhhdmUgYSB0cmFuc3BhcmVuY3kgbGV2ZWwgb2YgLjMuIAoKCiMjIyBEZW1vIHZpZGVvCgpXaXRoIHRoYXQgaW50cm9kdWN0aW9uLCB5b3UgYXJlIHJlYWR5IHRvIHdhdGNoIHRoZSBkZW1vIHZpZGVvIQoKPGlmcmFtZSB3aWR0aD0iNTYwIiBoZWlnaHQ9IjMxNSIgc3JjPSJodHRwczovL3d3dy55b3V0dWJlLmNvbS9lbWJlZC8yazhPLVlfdWlSVSIgZnJhbWVib3JkZXI9IjAiIGFsbG93PSJhY2NlbGVyb21ldGVyOyBhdXRvcGxheTsgY2xpcGJvYXJkLXdyaXRlOyBlbmNyeXB0ZWQtbWVkaWE7IGd5cm9zY29wZTsgcGljdHVyZS1pbi1waWN0dXJlIiBhbGxvd2Z1bGxzY3JlZW4+PC9pZnJhbWU+CgpbVm9pY2V0aHJlYWQ6IGBnZ21hcGAgZGVtb10oaHR0cHM6Ly92b2ljZXRocmVhZC5jb20vc2hhcmUvMTU0Nzk2ODgvKQoKYGBge3IsIGVjaG89RkFMU0V9CmRvd25sb2FkX2ZpbGUoCiAgcGF0aCA9ICIwNF9nZ21hcF9kZW1vX25vX2NvZGUuUm1kIiwKICBidXR0b25fbGFiZWwgPSAiRG93bmxvYWQgZ2dtYXAgZGVtbyBmaWxlICh3aXRob3V0IGNvZGUpIiwKICBidXR0b25fdHlwZSA9ICJ3YXJuaW5nIiwKICBoYXNfaWNvbiA9IFRSVUUsCiAgaWNvbiA9ICJmYSBmYS1zYXZlIiwKICBzZWxmX2NvbnRhaW5lZCA9IEZBTFNFCikKYGBgCgpgYGB7ciwgZWNobz1GQUxTRX0KZG93bmxvYWRfZmlsZSgKICBwYXRoID0gIjA0X2dnbWFwX2RlbW8uUm1kIiwKICBidXR0b25fbGFiZWwgPSAiRG93bmxvYWQgZ2dtYXAgZGVtbyBmaWxlICh3aXRoIGNvZGUpIiwKICBidXR0b25fdHlwZSA9ICJpbmZvIiwKICBoYXNfaWNvbiA9IFRSVUUsCiAgaWNvbiA9ICJmYSBmYS1zYXZlIiwKICBzZWxmX2NvbnRhaW5lZCA9IEZBTFNFCikKYGBgCgoKIyMjIFJlc291cmNlcwoKKiBbRXhhbXBsZXNdKGh0dHBzOi8vZ2l0aHViLmNvbS9ka2FobGUvZ2dtYXApIGZyb20gYGdnbWFwYCBtYWludGFpbmVyIERhdmlkIEthaGxlICAKKiBbYGdnbWFwYCBjaGVhdHNoZWV0XShodHRwczovL3d3dy5uY2Vhcy51Y3NiLmVkdS9zaXRlcy9kZWZhdWx0L2ZpbGVzLzIwMjAtMDQvZ2dtYXBDaGVhdHNoZWV0LnBkZikKCiMjIyBZb3VyIHR1cm4hCgojIyMjIEV4ZXJjaXNlOiBNb3JlIHdpdGggU3RhcmJ1Y2tzCgphLiBBZGQgYW4gYWVzdGhldGljIHRvIHRoZSB3b3JsZCBtYXAgdGhhdCBzZXRzIHRoZSBjb2xvciBvZiB0aGUgcG9pbnRzIGFjY29yZGluZyB0byB0aGUgb3duZXJzaGlwIHR5cGUuIFdoYXQsIGlmIGFueXRoaW5nLCBjYW4geW91IGRlZHVjZSBmcm9tIHRoaXMgdmlzdWFsaXphdGlvbj8gIAoKYi4gQ29uc3RydWN0IGEgbmV3IG1hcCBvZiBTdGFyYnVja3MgbG9jYXRpb25zIGluIHRoZSBUd2luIENpdGllcyBtZXRybyBhcmVhIChhcHByb3hpbWF0ZWx5IHRoZSA1IGNvdW50eSBtZXRybyBhcmVhKS4gIAoKYy4gSW4gdGhlIFR3aW4gQ2l0aWVzIHBsb3QsIHBsYXkgd2l0aCB0aGUgem9vbSBudW1iZXIuIFdoYXQgZG9lcyBpdCBkbz8gIChqdXN0IGRlc2NyaWJlIHdoYXQgaXQgZG9lcyAtIGRvbid0IGFjdHVhbGx5IGluY2x1ZGUgbW9yZSB0aGFuIG9uZSBtYXApLiAgCgpkLiBUcnkgYSBjb3VwbGUgZGlmZmVyZW50IG1hcCB0eXBlcyAoc2VlIGBnZXRfc3RhbWVubWFwKClgIGluIGhlbHAgYW5kIGxvb2sgYXQgYG1hcHR5cGVgKS4gSW5jbHVkZSBhIG1hcCB3aXRoIG9uZSBvZiB0aGUgb3RoZXIgbWFwIHR5cGVzLiAgCgplLiBBZGQgYSBwb2ludCB0byB0aGUgbWFwIHRoYXQgaW5kaWNhdGVzIE1hY2FsZXN0ZXIgQ29sbGVnZSBhbmQgbGFiZWwgaXQgYXBwcm9wcmlhdGVseS4gVGhlcmUgYXJlIG1hbnkgd2F5cyB5b3UgY2FuIGRvIHRoaW5rLCBidXQgSSB0aGluayBpdCdzIGVhc2llc3Qgd2l0aCB0aGUgYGFubm90YXRlKClgIGZ1bmN0aW9uIChzZWUgYGdncGxvdDJgIGNoZWF0c2hlZXQpLgoKIyMgQ2hvcm9wbGV0aHMKCkdlb2dyYXBoaWNhbCBkYXRhIG5lZWRuJ3QgYmUgZXhwcmVzc2VkIGJ5IGxhdGl0dWRlIGFuZCBsb25naXR1ZGUuIEZvciBjaG9yb3BsZXRoIG1hcHMsIGluc3RlYWQgb2YgdmlzdWFsaXppbmcgb3VyIGRhdGEgYXMgcG9pbnRzIHdpdGggZGlmZmVyZW50IGFlc3RoZXRpY3MgKHNpemUsIGNvbG9yLCB0cmFuc3BhcmVuY3ksIGV0Yy4pLCB3ZSBjb2xvciBkaWZmZXJlbnQgcmVnaW9ucyBvZiB0aGUgbWFwcyBiYXNlZCBvbiBkYXRhIHZhbHVlcy4gVG8gZG8gdGhpcyB3ZSBuZWVkIHRvIHNwZWNpZnkgYm90aCB0aGUgZ2VvbWV0cmljIHJlZ2lvbnMgb24gd2hpY2ggdGhlIGRhdGEgcmVzaWRlcyAoY291bnRpZXMsIHN0YXRlcywgemlwIGNvZGVzLCBldGMuKSwgYW5kIHRoZW4gd3JhbmdsZSB0aGUgZGF0YSBzbyB0aGF0IHRoZXJlIGlzIG9uZSB2YWx1ZSBwZXIgcmVnaW9uLiAKCkxldCdzIHJldHVybiB0byB0aGUgU3RhcmJ1Y2tzIGRhdGEuIEZpcnN0LCB3ZSB3aWxsIGNyZWF0ZSBhIG5ldyBkYXRhc2V0LCBgc3RhcmJ1Y2tzX3VzX2J5X3N0YXRlYCB0aGF0IGxpbWl0cyB0aGUgZGF0YSB0byB0aGUgVVMsIGZpbmRzIHRoZSBudW1iZXIgb2YgU3RhcmJ1Y2tzIGluIGVhY2ggc3RhdGUsIGFuZCBjcmVhdGVzIGEgc3RhdGUgbmFtZSB0aGF0IGlzIGluIGFsbCBsb3dlcmNhc2UgbGV0dGVycyB0aGF0IG1hdGNoZXMgdGhlIHN0YXRlIG5hbWUgaW4gdGhlIGByZWdpb25gIHZhcmlhYmxlIG9mIHRoZSBgc3RhdGVzX21hcGAgZGF0YXNldC4KClRoZSBgc3RhdGVzX21hcGAgZGF0YXNldCBnaXZlcyBpbmZvcm1hdGlvbiBhYm91dCBjcmVhdGluZyB0aGUgYm9yZGVycyBvZiB0aGUgVVMgc3RhdGVzLiBUaGUgZGF0YSBpcyByZXRyaWV2ZWQgdXNpbmcgdGhlIGBtYXBfZGF0YSgpYCBmdW5jdGlvbi4gUnVuIGA/bWFwX2RhdGFgIGluIHRoZSBjb25zb2xlIHRvIHNlZSBtb3JlIGluZm9ybWF0aW9uIGFib3V0IHdoYXQgb3RoZXIgbWFwcyBhcmUgYXZhaWxhYmxlLiBUaGVyZSBhcmUgYWxzbyBvdGhlciBwYWNrYWdlcyB0aGF0IHByb3ZpZGUgZGlmZmVyZW50IHR5cGVzIG9mIG1hcHMuCgpUaGVuLCB3ZSBjYW4gdXNlIGBnZW9tX21hcCgpYCB0byBjcmVhdGUgYSBjaG9yb3BsZXRoIG1hcC4gTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIG1hcCBhbmQgd2UnbGwgZ28gdGhyb3VnaCB0aGUgZGV0YWlscyBhZnRlci4KCmBgYHtyfQojQ3JlYXRlIGEgbmV3IFN0YXJidWNrcyBkYXRhc2V0IHRoYXQgCiMgLSBmaWx0ZXJzIHRvIHRoZSBVUwojIC0gc3VtbWFyaXplcyB0aGUgbnVtYmVyIG9mIFN0YXJidWNrcyBpbiBlYWNoIHN0YXRlCiMgLSBoYXMgZnVsbCBuYW1lcyBvZiBzdGF0ZXMgaW4gbG93ZXJjYXNlIGxldHRlcnMgKHRvIG1hdGNoIHRvIHN0YXRlc19tYXAgZGF0YSBjcmVhdGVkIG5leHQpCgpzdGFyYnVja3NfdXNfYnlfc3RhdGUgPC0gU3RhcmJ1Y2tzICU+JSAKICBmaWx0ZXIoQ291bnRyeSA9PSAiVVMiKSAlPiUgCiAgY291bnQoYFN0YXRlL1Byb3ZpbmNlYCkgJT4lIAogIG11dGF0ZShzdGF0ZV9uYW1lID0gc3RyX3RvX2xvd2VyKGFiYnIyc3RhdGUoYFN0YXRlL1Byb3ZpbmNlYCkpKSAKCiNVUyBzdGF0ZXMgbWFwIGluZm9ybWF0aW9uIC0gY29vcmRpbmF0ZXMgdXNlZCB0byBkcmF3IGJvcmRlcnMKc3RhdGVzX21hcCA8LSBtYXBfZGF0YSgic3RhdGUiKQoKIyBtYXAgdGhhdCBjb2xvcnMgc3RhdGUgYnkgbnVtYmVyIG9mIFN0YXJidWNrcwpzdGFyYnVja3NfdXNfYnlfc3RhdGUgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX21hcChtYXAgPSBzdGF0ZXNfbWFwLAogICAgICAgICAgIGFlcyhtYXBfaWQgPSBzdGF0ZV9uYW1lLAogICAgICAgICAgICAgICBmaWxsID0gbikpICsKICAjVGhpcyBhc3N1cmVzIHRoZSBtYXAgbG9va3MgZGVjZW50bHkgbmljZToKICBleHBhbmRfbGltaXRzKHggPSBzdGF0ZXNfbWFwJGxvbmcsIHkgPSBzdGF0ZXNfbWFwJGxhdCkgKyAKICB0aGVtZV9tYXAoKQpgYGAKCk5vdywgbGV0J3MgbG9vayBtb3JlIGNsb3NlbHkgYXQgd2hhdCBlYWNoIHBpZWNlIG9mIHRoZSBjb2RlIGJlbG93IGlzIGRvaW5nLgoKYGBge3Igc3RhcmJ1Y2tzLWNob3ItbWFwLCBldmFsPUZBTFNFfQpzdGFyYnVja3NfdXNfYnlfc3RhdGUgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX21hcChtYXAgPSBzdGF0ZXNfbWFwLAogICAgICAgICAgIGFlcyhtYXBfaWQgPSBzdGF0ZV9uYW1lLAogICAgICAgICAgICAgICBmaWxsID0gbikpICsKICBleHBhbmRfbGltaXRzKHggPSBzdGF0ZXNfbWFwJGxvbmcsIHkgPSBzdGF0ZXNfbWFwJGxhdCkgKyAKICB0aGVtZV9tYXAoKQpgYGAKCiMjIyBDaG9vc2UgYSBtYXAKClRoZSBgbWFwYCBhcmd1bWVudCB0ZWxscyBSIGF0IHdoaWNoIGxldmVsIHRvIGNyZWF0ZSB0aGUgbWFwLiBSZWFsbHksIGl0IHRlbGxzIGl0IGhvdyB0byBkcmF3IGFsbCB0aGUgYm9yZGVycyBUaGlzIGlzIGEgdmVyeSBzcGVjaWFsIGRhdGEgc2V0LiBBY2NvcmRpbmcgdG8gdGhlIGBnZW9tX21hcCgpYCBkb2N1bWVudGF0aW9uLCBpdCBpcyBhICJkYXRhIGZyYW1lIHRoYXQgY29udGFpbnMgdGhlIG1hcCBjb29yZGluYXRlcyAuLi4gSXQgKiptdXN0KiogY29udGFpbiBjb2x1bW5zIHggb3IgbG9uZywgeSBvciBsYXQsIGFuZCByZWdpb24gb3IgaWQuIiBXZSBhcmUgdXNpbmcgdGhlIGBtYXBfZGF0YSgpYCBmdW5jdGlvbiB0byBjcmVhdGUgdGhlIG1hcCBmaWxlIChzZWUgYWJvdmUgZm9yIG1vcmUgZGV0YWlsMC4gWW91IGNhbiBvcGVuIHRoZSBtYXAgZGF0YSwgYHN0YXRlc19tYXBgLCBhbmQgc2VlIHRoYXQgaXQgYWRoZXJlcyB0byB0aGUgcnVsZXMuCgpgYGB7ciwgZWNobz1GQUxTRX0KZGVjb3JhdGVfY2h1bmsoInN0YXJidWNrcy1jaG9yLW1hcCIsIGV2YWwgPSBGQUxTRSkgJT4lIAogIGZsYWlyKCJtYXAgPSAiKQpgYGAKCiMjIyBDb25uZWN0IG1hcCBpZC9yZWdpb24gdmFyaWFibGUgdG8gZGF0YSBiZWluZyBwbG90dGVkCgpUaGUgYG1hcF9pZGAgaW5zaWRlIG9mIGBhZXMoKWAgaXMgYSByZXF1aXJlZCBhZXN0aGV0aWMgZm9yIHRoZSBgZ2VvbV9tYXAoKWAgZ2VvbS4gSXQgdGVsbHMgUiB3aGljaCB2YXJpYWJsZSBpcyB0aGUgcmVnaW9uL2lkIHZhcmlhYmxlLCBpbiB0aGlzIGNhc2UgdGhlIHN0YXRlLiBJdCBjb25uZWN0cyB0aGUgYHJlZ2lvbmAgb3IgYGlkYCBmcm9tIHRoZSBtYXAgKGByZWdpb25gIHZhcmlhYmxlIGluIGBzdGF0ZXNfbWFwYCBkYXRhc2V0LCBpbiB0aGlzIGV4YW1wbGUpIHRvIHRoZSBkYXRhc2V0IGJlaW5nIHBsb3R0ZWQgKGBzdGF0ZV9uYW1lYCBpbiBgc3RhcmJ1Y2tzX3VzX2J5X3N0YXRlYCwgaW4gdGhpcyBleGFtcGxlKS4gU28gYHN0YXRlX25hbWVgIG5lZWRzIHRvIGhhdmUgdGhlIHNhbWUgZm9ybSBhcyBgcmVnaW9uYCwgd2hpY2ggaXMgd2h5IHdlIG1vZGlmaWVkIHRoZSBzdGF0ZSBuYW1lcyBpbiBgc3RhcmJ1Y2tzX3VzX2J5X3N0YXRlYC4KCmBgYHtyLCBlY2hvPUZBTFNFfQpkZWNvcmF0ZV9jaHVuaygic3RhcmJ1Y2tzLWNob3ItbWFwIiwgZXZhbCA9IEZBTFNFKSAlPiUgCiAgZmxhaXIoIm1hcF9pZCA9ICIpCmBgYAoKCiMjIyBVc2UgYGdncGxvdDJgIGZlYXR1cmVzCgpXZSB0ZWxsIGl0IHRvIGZpbGwgaW4gdGhlIHN0YXRlcyBieSB0aGUgdmFyaWFibGUgYG5gLCB0aGUgbnVtYmVyIG9mIFN0YXJidWNrcyBpbiBlYWNoIHN0YXRlLiBXaXRoIHRoZSBgZ2VvbV9tYXAoKWAgZ2VvbSwgaXQgd2lsbCBmaWxsIGluIHRoZSBib3JkZXJzIG9mIHRoZSByZWdpb25zIHdlIGRlZmluZWQgaW4gdGhlIGBtYXBgIGFyZ3VtZW50LgoKYGBge3IsIGVjaG89RkFMU0V9CmRlY29yYXRlX2NodW5rKCJzdGFyYnVja3MtY2hvci1tYXAiLCBldmFsID0gRkFMU0UpICU+JSAKICBmbGFpcigiZmlsbCA9ICIpCmBgYCAKCiMjIyBgZXhwYW5kX2xpbWl0cygpYAoKVXNlIGBleHBhbmRfbGltaXRzKClgIHRvIGFzc3VyZSB0aGF0IHRoZSBtYXAgY292ZXJzIHRoZSBlbnRpcmUgYXJlYSBpdCdzIHN1cHBvc2VkIHRvLiBXZSBwdXQgdGhlIGxvbmdpdHVkZSB2YXJpYWJsZSBmcm9tIGBzdGF0ZXNfbWFwYCBmb3IgdGhlIGB4YCBhcmd1bWVudCBhbmQgdGhlIGxhdGl0dWRlIHZhcmlhYmxlIGZyb20gYHN0YXRlc19tYXBgIGZvciB0aGUgYHlgIGFyZ3VtZW50IHRvIGFzc3VyZSB0aGUgbWFwIHN0cmV0Y2hlcyBhY3Jvc3MgdGhlIGVudGlyZSByYW5nZSBvZiBsb25naXR1ZGVzIGFuZCBsYXRpdHVkZXMgaW4gdGhlIG1hcC4gVGhlcmUgbWF5IGJlIGEgYmV0dGVyIHdheSB0byBkbyB0aGlzIGJ1dCBJIGhhdmUgeWV0IHRvIGZpbmQgaXQsIGFuZCB3aGVuIEkgbGVhdmUgaXQgb3V0LCBJIGRvbid0IGV2ZW4gc2VlIHRoZSBtYXAuIAoKYGBge3IsIGVjaG89RkFMU0V9CmRlY29yYXRlX2NodW5rKCJzdGFyYnVja3MtY2hvci1tYXAiLCBldmFsID0gRkFMU0UpICU+JSAKICBmbGFpcigiZXhwYW5kX2xpbWl0cyIpCmBgYAoKIyMjIGB0aGVtZV9tYXAoKWAKClRoaXMgaXMgYSBwZXJzb25hbCBwcmVmZXJlbmNlLiBJIGxpa2UgdGhlIHdheSBgdGhlbWVfbWFwKClgIG1ha2VzIHRoZSBtYXAgbG9vay4KCmBgYHtyLCBlY2hvPUZBTFNFfQpkZWNvcmF0ZV9jaHVuaygic3RhcmJ1Y2tzLWNob3ItbWFwIiwgZXZhbCA9IEZBTFNFKSAlPiUgCiAgZmxhaXIoInRoZW1lX21hcCgpIikKYGBgCgojIyMgQWRkIGBnZ3Bsb3QyYCBsYXllcnMKCllvdSBjYW4gYWRkIGFueSBvZiB0aGUgYGdncGxvdDJgIGxheWVycyBvbiB0b3Agb2YgdGhpcyBtYXAuIEluIHRoaXMgZXhhbXBsZSwgSSd2ZSBhZGRlZCB0aGUgTU4gU3RhcmJ1Y2tzIGFzIHBvaW50cywgYSB0aXRsZSwgYW5kIGNoYW5nZWQgdGhlIGxlZ2VuZCBiYWNrZ3JvdW5kIChzbyBpdCBkb2Vzbid0IGhhdmUgb25lIGFuZCBvdmVybGFwIENhbGlmb3JuaWEpLgoKYGBge3J9CnN0YXJidWNrc191c19ieV9zdGF0ZSAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21fbWFwKG1hcCA9IHN0YXRlc19tYXAsCiAgICAgICAgICAgYWVzKG1hcF9pZCA9IHN0YXRlX25hbWUsCiAgICAgICAgICAgICAgIGZpbGwgPSBuKSkgKwogIGdlb21fcG9pbnQoZGF0YSA9IFN0YXJidWNrcyAlPiUgZmlsdGVyKGBTdGF0ZS9Qcm92aW5jZWAgPT0gIk1OIiksCiAgICAgICAgICAgICBhZXMoeCA9IExvbmdpdHVkZSwgeSA9IExhdGl0dWRlKSwKICAgICAgICAgICAgIHNpemUgPSAuMDUsCiAgICAgICAgICAgICBhbHBoYSA9IC4yLCAKICAgICAgICAgICAgIGNvbG9yID0gImdvbGRlbnJvZCIpICsKICBleHBhbmRfbGltaXRzKHggPSBzdGF0ZXNfbWFwJGxvbmcsIHkgPSBzdGF0ZXNfbWFwJGxhdCkgKyAKICBsYWJzKHRpdGxlID0gIlN0YXJidWNrcyBpbiBNTiIpICsKICB0aGVtZV9tYXAoKSArCiAgdGhlbWUobGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKIyMjIERlbW8gdmlkZW8KCkFuZCB5b3UgYXJlIHJlYWR5IHRvIHdhdGNoIGFub3RoZXIgZGVtbyB2aWRlbyEKCjxpZnJhbWUgd2lkdGg9IjU2MCIgaGVpZ2h0PSIzMTUiIHNyYz0iaHR0cHM6Ly93d3cueW91dHViZS5jb20vZW1iZWQvaVM1OWE1d0RyRU0iIGZyYW1lYm9yZGVyPSIwIiBhbGxvdz0iYWNjZWxlcm9tZXRlcjsgYXV0b3BsYXk7IGNsaXBib2FyZC13cml0ZTsgZW5jcnlwdGVkLW1lZGlhOyBneXJvc2NvcGU7IHBpY3R1cmUtaW4tcGljdHVyZSIgYWxsb3dmdWxsc2NyZWVuPjwvaWZyYW1lPgoKW1ZvaWNldGhyZWFkOiBgZ2VvbV9tYXAoKWAgZGVtb10oaHR0cHM6Ly92b2ljZXRocmVhZC5jb20vc2hhcmUvMTU0ODQ3OTQvKQoKYGBge3IsIGVjaG89RkFMU0V9CmRvd25sb2FkX2ZpbGUoCiAgcGF0aCA9ICIwNF9nZW9tX21hcF9kZW1vX25vX2NvZGUuUm1kIiwKICBidXR0b25fbGFiZWwgPSAiRG93bmxvYWQgZ2VvbV9tYXAoKSBkZW1vIGZpbGUgKHdpdGhvdXQgY29kZSkiLAogIGJ1dHRvbl90eXBlID0gIndhcm5pbmciLAogIGhhc19pY29uID0gVFJVRSwKICBpY29uID0gImZhIGZhLXNhdmUiLAogIHNlbGZfY29udGFpbmVkID0gRkFMU0UKKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFfQpkb3dubG9hZF9maWxlKAogIHBhdGggPSAiMDRfZ2VvbV9tYXBfZGVtby5SbWQiLAogIGJ1dHRvbl9sYWJlbCA9ICJEb3dubG9hZCBnZW9tX21hcCgpIGRlbW8gZmlsZSAod2l0aCBjb2RlKSIsCiAgYnV0dG9uX3R5cGUgPSAiaW5mbyIsCiAgaGFzX2ljb24gPSBUUlVFLAogIGljb24gPSAiZmEgZmEtc2F2ZSIsCiAgc2VsZl9jb250YWluZWQgPSBGQUxTRQopCmBgYAoKCiMjIyBSZXNvdXJjZXMKCiogW2dncGxvdDIgZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL2dlb21fbWFwLmh0bWwpICAKKiBbRXhhbXBsZV0oaHR0cHM6Ly9yc3R1ZGlvLXB1YnMtc3RhdGljLnMzLmFtYXpvbmF3cy5jb20vNzgxNDhfNmRkNDliNWRhYjRjNGY1YThiMWE3NGU1ODkzZmYxN2QuaHRtbCkgYnkgQXJpZSBWb29ybWFuIChzb21lIHRoaW5ncyBjb3VsZCBiZSBvdXQgb2YgZGF0ZSBzaW5jZSBpdCdzIGZyb20gMjAxNSkKCiMjIyBZb3VyIHR1cm4hCgojIyMjIEV4ZXJjaXNlOiBFdmVuIG1vcmUgd2l0aCBTdGFyYnVja3MKClRoZSBleGFtcGxlIEkgc2hvd2VkIGRpZCBub3QgYWNjb3VudCBmb3IgcG9wdWxhdGlvbiBvZiBlYWNoIHN0YXRlIGluIHRoZSBtYXAuIEluIHRoZSBjb2RlIGJlbG93LCBhIG5ldyB2YXJpYWJsZSBpcyBjcmVhdGVkLCBgc3RhcmJ1Y2tzX3Blcl8xMDAwMGAsIHRoYXQgZ2l2ZXMgdGhlIG51bWJlciBvZiBTdGFyYnVja3MgcGVyIDEwLDAwMCBwZW9wbGUuIEl0IGlzIGluIHRoZSBgc3RhcmJ1Y2tzX3dpdGhfMjAxOF9wb3BfZXN0YCBkYXRhc2V0LgoKYGBge3J9CmNlbnN1c19wb3BfZXN0XzIwMTggPC0gcmVhZF9jc3YoImh0dHBzOi8vd3d3LmRyb3Bib3guY29tL3MvNnR4d3YzYjRuZzdwZXBlL3VzX2NlbnN1c18yMDE4X3N0YXRlX3BvcF9lc3QuY3N2P2RsPTEiKSAlPiUgCiAgc2VwYXJhdGUoc3RhdGUsIGludG8gPSBjKCJkb3QiLCJzdGF0ZSIpLCBleHRyYSA9ICJtZXJnZSIpICU+JSAKICBzZWxlY3QoLWRvdCkgJT4lIAogIG11dGF0ZShzdGF0ZSA9IHN0cl90b19sb3dlcihzdGF0ZSkpCgpzdGFyYnVja3Nfd2l0aF8yMDE4X3BvcF9lc3QgPC0KICBzdGFyYnVja3NfdXNfYnlfc3RhdGUgJT4lIAogIGxlZnRfam9pbihjZW5zdXNfcG9wX2VzdF8yMDE4LAogICAgICAgICAgICBieSA9IGMoInN0YXRlX25hbWUiID0gInN0YXRlIikpICU+JSAKICBtdXRhdGUoc3RhcmJ1Y2tzX3Blcl8xMDAwMCA9IChuL2VzdF9wb3BfMjAxOCkqMTAwMDApCmBgYAoKYS4gKipgZHBseXJgIHJldmlldyoqOiBMb29rIHRocm91Z2ggdGhlIGNvZGUgYWJvdmUgYW5kIGRlc2NyaWJlIHdoYXQgZWFjaCBsaW5lIG9mIGNvZGUgZG9lcy4KCmIuIENyZWF0ZSBhIGNob3JvcGxldGggbWFwIHRoYXQgc2hvd3MgdGhlIG51bWJlciBvZiBTdGFyYnVja3MgcGVyIDEwLDAwMCBwZW9wbGUgb24gYSBtYXAgb2YgdGhlIFVTLiBVc2UgYSBuZXcgZmlsbCBjb2xvciwgYWRkIHBvaW50cyBmb3IgYWxsIFN0YXJidWNrcyBpbiB0aGUgVVMgKGV4Y2VwdCBIYXdhaWkgYW5kIEFsYXNrYSksIGFkZCBhbiBpbmZvcm1hdGl2ZSB0aXRsZSBmb3IgdGhlIHBsb3QsIGFuZCBpbmNsdWRlIGEgY2FwdGlvbiB0aGF0IHNheXMgd2hvIGNyZWF0ZWQgdGhlIHBsb3QgKHlvdSEpLiBNYWtlIGEgY29uY2x1c2lvbiBhYm91dCB3aGF0IHlvdSBvYnNlcnZlLgoKCiMjIFVzaW5nIGBsZWFmbGV0YCB0byBjcmVhdGUgbWFwcwoKIyMjIENvbmNlcHQgTWFwCgohW10oLi4vLi4vaW1hZ2VzL2xlYWZsZXRfY29uY2VwdF9tYXAucG5nKQoKW0xlYWZsZXRdKGh0dHBzOi8vbGVhZmxldGpzLmNvbS8pIGlzIGFuIG9wZW4tc291cmNlIEphdmFTY3JpcHQgbGlicmFyeSBmb3IgY3JlYXRpbmcgbWFwcy4gSXQgY2FuIGJlIHVzZWQgb3V0c2lkZSBvZiBSLCBidXQgd2Ugd2lsbCBvbmx5IGRpc2N1c3MgdXNpbmcgdGhlIGBsZWFmbGV0YCBsaWJyYXJ5IGluIFIuIAoKVGhpcyBsaWJyYXJ5IHVzZXMgYSBkaWZmZXJlbnQgcGxvdHRpbmcgZnJhbWV3b3JrIGZyb20gYGdncGxvdDJgIGFsdGhvdWdoIGl0IHN0aWxsIGhhcyBhIGB0aWR5dmVyc2VgIGZlZWwgZHVlIHRvIGl0cyB1c2Ugb2YgdGhlIHBpcGUsIGAlPiVgIGFuZCB0aGUgd2F5IGl0IGFkZHMgbGF5ZXJzIHRvIHRoZSBwbG90LCBqdXN0IGxpa2UgaW4gYGdncGxvdDJgLgoKIyMjIEludHJvZHVjdG9yeSB2aWRlbwoKV2F0Y2ggdGhlIHZpZGVvIHRoYXQgaW50cm9kdWNlcyBgbGVhZmxldCgpYCBmdW5jdGlvbnMuCgo8aWZyYW1lIHdpZHRoPSI1NjAiIGhlaWdodD0iMzE1IiBzcmM9Imh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL2VtYmVkL3c1VTYyd1VraTNFIiBmcmFtZWJvcmRlcj0iMCIgYWxsb3c9ImFjY2VsZXJvbWV0ZXI7IGF1dG9wbGF5OyBjbGlwYm9hcmQtd3JpdGU7IGVuY3J5cHRlZC1tZWRpYTsgZ3lyb3Njb3BlOyBwaWN0dXJlLWluLXBpY3R1cmUiIGFsbG93ZnVsbHNjcmVlbj48L2lmcmFtZT4KCltWb2ljZXRocmVhZDogTWFwcGluZyBpbiBSIHdpdGggbGVhZmxldF0oaHR0cHM6Ly92b2ljZXRocmVhZC5jb20vc2hhcmUvMTU1MjY2MzQvKQoKYGBge3IsIGVjaG89RkFMU0V9CmRvd25sb2FkX2ZpbGUoCiAgcGF0aCA9ICIwNF9sZWFmbGV0X2ludHJvLmh0bWwiLAogIGJ1dHRvbl9sYWJlbCA9ICJEb3dubG9hZCBzbGlkZXMiLAogIGJ1dHRvbl90eXBlID0gImluZm8iLAogIGhhc19pY29uID0gVFJVRSwKICBpY29uID0gImZhIGZhLXNhdmUiLAogIHNlbGZfY29udGFpbmVkID0gRkFMU0UKKQpgYGAKCiMjIyMgU3RlcHMgdG8gY3JlYXRlIGEgbWFwCgoxLiBDcmVhdGUgYSBtYXAgd2lkZ2V0IGJ5IGNhbGxpbmcgYGxlYWZsZXQoKWAgYW5kIHRlbGxpbmcgaXQgdGhlIGRhdGEgdG8gdXNlLiAgCjIuIEFkZCBhIGJhc2UgbWFwIHVzaW5nIGBhZGRUaWxlcygpYCAodGhlIGRlZmF1bHQpIG9yIGBhZGRQcm92aWRlclRpbGVzKClgLgozLiBBZGQgbGF5ZXJzIHRvIHRoZSBtYXAgYnkgdXNpbmcgbGF5ZXIgZnVuY3Rpb25zIChlLmcuICwgYGFkZE1hcmtlcnMoKWAsIGBhZGRQb2x5Z29ucygpYCkgdG8gbW9kaWZ5IHRoZSBtYXAgd2lkZ2V0LiAgIAo0LiBSZXBlYXQgc3RlcCAzIGFzIGRlc2lyZWQuICAKNS4gUHJpbnQgdGhlIG1hcCB3aWRnZXQgdG8gZGlzcGxheSBpdC4KCiMjIyMgQ3JlYXRpbmcgYSBtYXAKCk5vdywgSSBjcmVhdGUgYSBiYXNpYyBtYXAgYW5kIGFkZCBteSBwb2ludHMgKHRoZSBwb2ludHMgYXJlIGEgbGF5ZXIgb24gdGhlIG1hcCkuIFRoZSBkYXRhIGFyZSBpbiBgZmF2b3JpdGVfc3RwX2J5X2xpc2FgLiAKClRoZSBmdW5jdGlvbiB3ZSB3aWxsIHVzZSB0byBjcmVhdGUgdGhlIG1hcHMgd2lsbCBsb29rIGZvciBjZXJ0YWluIHZhcmlhYmxlIG5hbWVzIGZvciBsYXRpdHVkZSAobGF0LCBsYXRpdHVkZSkgYW5kIGxvbmdpdHVkZSAobG5nLCBsb25nLCBvciBsb25naXR1ZGUpLiBJZiB5b3UgZG8gbm90IG5hbWUgdGhlbSBvbmUgb2YgdGhvc2UgdGhpbmdzIG9yIGlmIHRoZSBkYXRhIHlvdSBhcmUgdXNpbmcgZG9lc24ndCBuYW1lIHRoZW0gdGhhdCwgeW91IG5lZWQgdG8gY2FsbCBvdXQgdGhlIG5hbWUgZXhwbGljaXRseS4gWW91IGNhbiB1c2UgYSAidHdvLWZpbmdlciBzY3JvbGwiIHRvIHpvb20gaW4gYW5kIG91dC4KCmBgYHtyfQpsZWFmbGV0KGRhdGEgPSBmYXZvcml0ZV9zdHBfYnlfbGlzYSkgJT4lICNiYXNlIHBsb3QKICBhZGRUaWxlcygpICU+JSAjYmFzZSBtYXAgLSBkZWZhdWx0IGlzIG9wZW5zdHJlZXQgbWFwIGJ1dCB0aGF0IGNhbiBiZSBjaGFuZ2VkCiAgYWRkTWFya2VycygpICNBZGRzIG1hcmtlcnMgLSBrbm93cyBsYXQgYW5kIGxvbmcgZnJvbSBuYW1lcyBpbiBkYXRhCmBgYAoKU2FtZSBhcyBhYm92ZSBidXQgZXhwbGljaXRseSB0b2xkIGl0IGxhdGl0dWRlIGFuZCBsb25naXR1ZGUsIHdoaWNoIHlvdSB3b3VsZCBuZWVkIHRvIGRvIGlmIHRob3NlIHZhcmlhYmxlcyBoYWQgYSBuYW1lIG5vdCByZWNvZ25pemVkIGJ5IHRoZSBmdW5jdGlvbiwgYW5kIGFkZGVkIGxhYmVscy4gKipXQVJOSU5HOiBETyBOT1QgRk9SR0VUIFRIRSB+IEJFRk9SRSBUSEUgVkFSSUFCTEUgTkFNRVMhISEqKiAKCmBgYHtyfQpsZWFmbGV0KGRhdGEgPSBmYXZvcml0ZV9zdHBfYnlfbGlzYSkgJT4lIAogIGFkZFRpbGVzKCkgJT4lIAogIGFkZE1hcmtlcnMobG5nID0gfmxvbmcsIGxhdCA9IH5sYXQsIGxhYmVsID0gfnBsYWNlKSAKYGBgCgpXZSBjYW4gY2hhbmdlIGp1c3QgYWJvdXQgZXZlcnl0aGluZyBhYm91dCBvdXIgbWFwLiBUaGlzIGlzIHRoZSBzYW1lIHBsb3QgYXMgYWJvdmUgd2l0aCBzb21lIGFlc3RoZXRpYyBjaGFuZ2VzLiBTb21lIHRpcHM6CgoqIFRvIHNlZSBhbGwgYXZhaWxhYmxlIHByb3ZpZGVyIGJhc2UgbWFwcywgdHlwZSBgcHJvdmlkZXJzYCBpbiB0aGUgY29uc29sZS4gCgoqIFRvIGFjY2VzcyB0aG9zZSBtYXBzLCB1c2UgYHByb3ZpZGVycyRQUk9WSURFUk5BTUVgIGluc2lkZSB0aGUgYGFkZFByb3ZpZGVyVGlsZXMoKWAgZnVuY3Rpb24sIHdoZXJlIGBQUk9WSURFUk5BTUVgIGlzIG9uZSBvZiB0aG9zZSBsaXN0ZWQgYHByb3ZpZGVyc2AuIFdoZW4geW91IHR5cGUgYHByb3ZpZGVyJGAgYSBsaXN0IHNob3VsZCBzaG93IHVwIHRoYXQgeW91IGNhbiBjbGljayBvbi4gCgoqIENvbG9ycyBuZWVkIHRvIGJlIGluICJoZXgiIGZvcm0uIEkgdXNlZCB0aGUgYGNvbDJoZXgoKWAgZnVuY3Rpb24gZnJvbSB0aGUgYGdwbG90YCBsaWJyYXJ5IHRvIGRvIHRoYXQgc2luY2UgSSBkb24ndCBoYXZlIGFueSBoZXggY29sb3JzIG1lbW9yaXplZC4gWW91IGNhbiBhbHNvIGdvb2dsZSBoZXggY29sb3JzIGFuZCBmaW5kIHdlYnNpdGVzIHRvIGhlbHAgeW91IG91dC4gCgoqIFNlYXJjaCBgYWRkQ29udHJvbGAgaW4gdGhlIEhlbHAgb3IgdHlwZSBgP2FkZENvbnRyb2xgIGludG8gdGhlIGNvbnNvbGUgdG8gc2VlIHdoYXQgYWxsIHRoZSBhcmd1bWVudHMgbWVhbiBhbmQgaG93IHlvdSBjYW4gY2hhbmdlIHRoZW0uCgpgYGB7cn0KbGVhZmxldChkYXRhID0gZmF2b3JpdGVfc3RwX2J5X2xpc2EpICU+JSAKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRTdGFtZW4uV2F0ZXJjb2xvcikgJT4lIAogIGFkZENpcmNsZXMobG5nID0gfmxvbmcsIAogICAgICAgICAgICAgbGF0ID0gfmxhdCwgCiAgICAgICAgICAgICBsYWJlbCA9IH5wbGFjZSwgCiAgICAgICAgICAgICB3ZWlnaHQgPSAxMCwgCiAgICAgICAgICAgICBvcGFjaXR5ID0gMSwgY29sb3IgPSBjb2wyaGV4KCJkYXJrYmx1ZSIpKSAKYGBgCgpUaGUgbWFwIGJlbG93IGlzIGFsc28gdGhlICJzYW1lIiBhcyB0aGUgb25lcyBJIGhhdmUgYWxyZWFkeSBjcmVhdGVkIHdpdGggYSBuZXcgYmFzZSBtYXAgYW5kIGEgbGluZSB0byB0cmFjZSBteSByb3V0ZSwgd2hpY2ggd2FzIGNyZWF0ZWQgd2l0aCB0aGUgYGFkZFBvbHlsaW5lcygpYCBsYXllci4gSXQgdHJhY2VzIGl0IGluIHRoZSBvcmRlciB0aGV5IGFyZSBlbnRlcmVkIGluIHRoZSBkYXRhc2V0LgoKYGBge3J9CmxlYWZsZXQoZGF0YSA9IGZhdm9yaXRlX3N0cF9ieV9saXNhKSAlPiUgCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5EYXJrTWF0dGVyKSAlPiUgCiAgYWRkQ2lyY2xlcyhsbmcgPSB+bG9uZywgCiAgICAgICAgICAgICBsYXQgPSB+bGF0LCAKICAgICAgICAgICAgIGxhYmVsID0gfnBsYWNlLCAKICAgICAgICAgICAgIHdlaWdodCA9IDEwLCAKICAgICAgICAgICAgIG9wYWNpdHkgPSAxLCAKICAgICAgICAgICAgIGNvbG9yID0gY29sMmhleCgiZGFya3JlZCIpKSAlPiUgCiAgYWRkUG9seWxpbmVzKGxuZyA9IH5sb25nLCAKICAgICAgICAgICAgICAgbGF0ID0gfmxhdCwgCiAgICAgICAgICAgICAgIGNvbG9yID0gY29sMmhleCgiZGFya3JlZCIpKQpgYGAKCiMjIyMgQ2hvcm9wbGV0aCBsYXllcnMgd2l0aCBgYWRkUG9seWdvbnMoKWAKCgoKIyMjIFJlc291cmNlcwoKKiBbRGV0YWlsZWQgZG9jdW1lbmF0aW9uXShodHRwczovL3JzdHVkaW8uZ2l0aHViLmlvL2xlYWZsZXQvKSAod2l0aCBleGFtcGxlcykKCiogW0NoZWF0c2hlZXRdKGh0dHBzOi8vdWdvcHJvdG8uZ2l0aHViLmlvL3Vnb19yX2RvYy9wZGYvbGVhZmxldC1jaGVhdC1zaGVldC5wZGYpCgoqIFtQcm92aWRlciBtYXAgcHJldmlld3NdKGh0dHA6Ly9sZWFmbGV0LWV4dHJhcy5naXRodWIuaW8vbGVhZmxldC1wcm92aWRlcnMvcHJldmlldy8pCgo=